#include <sys/time.h> 
#include "gateway.h"

/**--------------------------------------------------------------------------------
* 函数名称 :	SetTcpKeepalive
* 功能描述 :	设置套接字心跳包
* 入口参数 :	int fd    套接字文件描述符
				int start 首次心跳侦测包发送之间的空闲时间    
				int interval 两次心跳侦测包之间的间隔时间   
				int count 探测次数，即将几次探测失败判定为TCP断开  
* 编辑作者 :	
* 返回值   ：   返回0：成功  -1：失败
* 备  注   ：   
---------------------------------------------------------------------------------*/
int SetTcpKeepalive(int fd, unsigned int start, 
					unsigned int interval, unsigned int count)     
{     
    int keepAlive = 1;     
    if (fd < 0)
		return -1; 
	
    //启用心跳机制，如果您想关闭，将keepAlive置零即可     
    if(setsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1)     
    {     
        perror("setsockopt");     
        return -1;     
    }     
    //启用心跳机制开始到首次心跳侦测包发送之间的空闲时间     
    if(setsockopt(fd,SOL_TCP,TCP_KEEPIDLE,(void *)&start,sizeof(start)) == -1)     
    {     
        perror("setsockopt");     
        return -1;     
    }     
    //两次心跳侦测包之间的间隔时间     
    if(setsockopt(fd,SOL_TCP,TCP_KEEPINTVL,(void *)&interval,sizeof(interval)) == -1)     
    {     
        perror("setsockopt");     
        return -1;     
    }     
    //探测次数，即将几次探测失败判定为TCP断开     
    if(setsockopt(fd,SOL_TCP,TCP_KEEPCNT,(void *)&count,sizeof(count)) == -1)     
    {     
        perror("setsockopt");     
        return -1;     
    }     
    return 0;     
}   

/**--------------------------------------------------------------------------------
* 函数名称 :	ServerInit
* 功能描述 :	服务器初始化
* 入口参数 :	char *ip    服务器IP地址    
				int port    服务器端口号   
* 编辑作者 :	
* 返回值   ：   成功返回socket套接字描述符  失败返回-1
* 备  注   ：   
---------------------------------------------------------------------------------*/
int ServerInit(const char *ip, const int port)
{
	int on = 1;
	struct sockaddr_in addr;   //服务器地址
	int sockfd = -1;
	int ret;
	
	if(!ip || !sockfd || (port<0)){
		printf("please input correct param \n");
		return -1;
	}

	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0){
		perror("socket");
		return -1;
	}

	ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));//设置地址重用
	if(ret < 0){
		perror("ServerInit  setsockopt");
		return -1;
	}
	
	addr.sin_family = AF_INET; //agreement
	addr.sin_port = htons(port); //port
	addr.sin_addr.s_addr = inet_addr(ip); //IP adress

	if(bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)) == -1){
		perror("bind");
		return -1;
	}
	//监听客户端
	ret = listen(sockfd,64);  
	if(ret < 0){
		perror("listen");
		return -1;
	}

	return sockfd;
}

/**--------------------------------------------------------------------------------
* 函数名称 :	ClientInit
* 功能描述 :	客户端初始化
* 入口参数 :	char *ip    服务器IP地址    
				int port    服务器端口号   
* 编辑作者 :	
* 返回值   ：   socket文件描述符 ， 若为-1则失败
* 备  注   ：   
---------------------------------------------------------------------------------*/
int ClientInit(const char *ip, const int port)
{
	int ret;
	int fd;
	int on = 1;//status
	struct sockaddr_in ser_addr;   //address
	
	if(!ip || (port<0)){
		printf("ClientInit:please input correct param \n");
		return -1;
	}

	fd = socket(AF_INET,SOCK_STREAM,0);
	if(fd < 0){
		perror("ClientInit socket");
		return -1;
	}
	
	ret = setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));//设置地址重用
	if(ret < 0){
		perror("ClientInit setsockopt");
		return -1;
	}
	
	ser_addr.sin_family = AF_INET; //agreement
	ser_addr.sin_port = htons(port); //port
	ser_addr.sin_addr.s_addr = inet_addr(ip); //IP adress
	
	ret = connect(fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
	if(ret < 0){
		//perror("connect");
		close(fd);
		return -1;
	}
	
	return fd;
}

/**--------------------------------------------------------------------------------
* 函数名称 :	SetNonblockMode
* 功能描述 :	设置为非阻塞模式
* 入口参数 :	int fd   文件描述符  
* 编辑作者 :	
* 返回值   ：   成功返回：0  失败返回：-1  
* 备  注   ：   
---------------------------------------------------------------------------------*/
int	 SetNonblockMode(int fd)
{
	int 	flags;
	if(fd < 0){
		return -1;
	}
	flags = fcntl(fd, F_GETFL, 0);            //获取文件的flags值。
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式
	return 0;
}

/**--------------------------------------------------------------------------------
* 函数名称 :	ReadEvent
* 功能描述 :	读事件，接收客户端数据
* 入口参数 :	int fd    连接套接字
				LinkData *data  存储读取数据的地址
* 编辑作者 :	
* 返回值   ：   成功返回0， 失败返回-1， 参数错误返回-2
* 备  注   ：   
---------------------------------------------------------------------------------*/
int ReadEvent(const int fd, LinkData *data)
{
	int ret;
	char buf[128] = {0};
	
	if(fd < 0 || !data){
		printf("no such file discription \n");
		return -2;
	}
	
	ret = recv(fd, buf, sizeof(buf), 0);
	if(ret>0){
		bzero(data, sizeof(LinkData));
		data->fd = fd;
		data->len = strlen(buf);
		data->flag = 1;
		memcpy(data->buf, buf, sizeof(data->buf)-1);
		
		return 0;
	}else{
		printf("client end,ret = %d,errno = %d, fd = %d \n",ret, errno, fd);
		close(fd);
		return -1;
	}
}

/**--------------------------------------------------------------------------------
* 函数名称 :	ListenEvent
* 功能描述 :	监听事件，监听客户端的连接
				epoll列表中
* 入口参数 :	int sockfd    socket文件描述符
				int epfd  epoll文件描述符

* 编辑作者 :	
* 返回值   ：   成功返回连接套接字， 失败返回-1
* 备  注   ：   
---------------------------------------------------------------------------------*/
int ListenEvent(const int sockfd)
{
	int connfd;
	
	if(sockfd < 0){
		printf("error discription \n");
		return -1;
	}
	
	connfd = accept(sockfd,NULL,NULL);//wait answer
	if(connfd<=0){
		perror("accept");
		return -1;
	}
	printf("connect success fd = %d \n",connfd);
	
	return connfd;
}

/**--------------------------------------------------------------------------------
* 函数名称 :	AddEvent
* 功能描述 :	将文件描述符添加到epoll列表中(注册事件)
* 入口参数 :	int fd    要添加的文件描述符
				int epfd  epoll文件描述符
				int flag  关注fd变化的方式
* 编辑作者 :	
* 返回值   ：   成功返回0， 失败返回-1
* 备  注   ：   flag的取值(以下的值可取或，例如  flag = EPOLLIN | EPOLLOUT)
				EPOLLIN     //表示对应的文件描述符可以读（包括对端SOCKET正常关闭）；
				EPOLLOUT    //表示对应的文件描述符可以写；
				EPOLLPRI    //表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；
				EPOLLERR    //表示对应的文件描述符发生错误；
				EPOLLHUP    //表示对应的文件描述符被挂断；
				EPOLLET     //将EPOLL设为边缘触发(Edge Triggered)模式，
					这是相对于水平触发(Level Triggered)来说的。
				EPOLLONESHOT//只监听一次事件，当监听完这次事件之后，
					如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里
---------------------------------------------------------------------------------*/
int AddEvent(int fd, int epfd, int flag)
{
	//用于注册事件
	struct epoll_event  ev = {0};  
	
	if(fd < 0 || epfd < 0){
		return -1;
	}
	ev.events = flag;         
	ev.data.fd = fd;  
	//将fd拷贝到内核
	epoll_ctl(epfd,EPOLL_CTL_ADD,fd, &ev);
				
	return 0;
}

/**-----------------------------------------------------------------------------------------------
* 函数名称 :	delay
* 功能描述 :	延时
* 入口参数 :	sec -秒
				ms  -毫秒
* 编辑作者 :	
* 返回值   ：  无
* 备  注   ： 
------------------------------------------------------------------------------------------------*/
void delay(unsigned int sec, unsigned int ms)
{
	struct  timeval  tv = {
		.tv_sec  = sec,
		.tv_usec = (1000*ms),
	};

	select(0, NULL,NULL, NULL, &tv);
	
}

void PrintTime(char *p)
{
	struct timeval tv = {0};
	
	gettimeofday(&tv, NULL);
	printf("%s  s:%ld  us:%ld \n", p, tv.tv_sec, tv.tv_usec);
}